package com.googlecode.cswish.util.bean;

import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.cglib.proxy.Enhancer;

import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.ObjectFactory;


/**
 * 
 * <b>Description:</b><br>
 * iterate each model property
 * 
 * <p>
 * <b>Author:</b><br>
 * t311330
 * <p>
 */
public class ModelScanner {

	private static final Log LOGGER = LogFactory.getLog(ModelScanner.class);

	private final static BeanIntrospector beanIntrospector = (BeanIntrospector) ObjectFactory.getInstance()
			.getBean(BeanIntrospector.class.getName(), false);

	private final Object[] BLANK_PARAM = new Object[0];

	private final static ModelScanner instance = new ModelScanner();

	public static ModelScanner getInstance() {
		return instance;
	}

	public void scan(Object model, Object destModel, ScannerCallback callback) {
		scan(model, destModel, null, callback, new IdentityHashMap<Object, Object>());
	}

	public ScannerFlag scan(Object srcModel, Object destModel, ScannerContext parentContext, ScannerCallback callback,
			Map<Object, Object> identityMap) {
		if (parentContext != null && parentContext.getDeep() > 10) {
			LOGGER.error("Scan level is too deep (larger than 10), stop it in :" + parentContext.getChildPath());
			return ScannerFlag.SCANSIBLING;
		}
		
		ScannerFlag flag = ScannerFlag.SCANSIBLING;
		// check the duplicated element
		if (identityMap.containsKey(srcModel)) {
			return flag;
		} else {
			identityMap.put(srcModel, destModel);
		}

		String parentPath = parentContext == null ? null : parentContext.getChildPath();
		if (srcModel instanceof Collection) {
			// scan list
			flag = scanChildren((Collection) srcModel, (Collection) destModel, parentContext, callback, identityMap);
		} else {
			Class srcClass = srcModel.getClass();
			if (Enhancer.isEnhanced(srcClass)) {
				srcClass = srcClass.getSuperclass();
			} else if (HibernateProxy.class.isAssignableFrom(srcClass)) {
				srcClass = Hibernate.getClass(srcModel);
			}
			
			// scan normal object
			ModelInfo modelInfo = beanIntrospector.getModelInfo(srcClass.getName());
			boolean onlyStaticProperty = false;
			if (ModelInfo.isDynamicClass(modelInfo.getGenericType()) && !ModelInfo.isDynamicClass(srcModel.getClass().getName())) {
				onlyStaticProperty = true;
			}
			
			Collection<PropertyInfo> descriptors = modelInfo.getAllProperties();
			ScannerContext scannerContext = new ScannerContext(parentContext);
			scannerContext.setModelInfo(modelInfo);
			scannerContext.setSrcModel(srcModel);
			scannerContext.setDestModel(destModel);
			scannerContext.setParentPath(parentPath);

			for (PropertyInfo descriptor : descriptors) {
				if (onlyStaticProperty && descriptor.isDynamic()) {
					continue;
				}
				
				if (descriptor.getReadMethod() != null && descriptor.getWriteMethod() != null) {
					scannerContext.setPropertyDescriptor(descriptor);
					ScannerFlag scannerFlag = callback.proceed(scannerContext);
					if (scannerFlag == ScannerFlag.SCANCHILD && scannerContext.getSrcPropertyValue() != null) {
//						if (srcModel.getClass().getName().equals("com.espirit.eap.manager.RevisionHistory")){
//							System.out.println("test");
//						}
						Object src = scannerContext.getSrcPropertyValue();
						Object dest = scannerContext.getDestPropertyValue();
						scan(src, dest, scannerContext, callback, identityMap);
					} else if (scannerFlag == ScannerFlag.STOP) {
						flag = scannerFlag;
						break;
					}
				}
			}
		}
		
		return flag;
	}

	private ScannerFlag scanChildren(Collection<Object> srcModels, Collection<Object> destModels, ScannerContext parentContext,
			ScannerCallback callback, Map<Object, Object> identityMap) {
		ScannerFlag flag = ScannerFlag.SCANSIBLING;
		if (destModels != null) {
			int index = 0;
			for (Iterator<Object> iterator = srcModels.iterator(), destIter = destModels.iterator(); iterator
					.hasNext()
					&& destIter.hasNext();) {
				Object srcChild = (Object) iterator.next();
				if (srcChild != null) {
					parentContext.setSubscript(index);
					flag = scan(srcChild, destIter.next(), parentContext, callback, identityMap);
					if (flag == ScannerFlag.STOP) {
						break;
					}
				}
				index++;
			}
			parentContext.setSubscript(-1);			// restore the original value
		}
		return flag;
	}

	public interface ScannerCallback {
		ScannerFlag proceed(ScannerContext scannerContext);
	}

	public enum ScannerFlag {
		SCANCHILD, SCANSIBLING, STOP
	}
}
